package cn.sjx.modules.nbiot.util;

import cn.hutool.core.util.HexUtil;

import java.util.Arrays;

/**
 * 基于Modbus CRC16的校验算法
 *
 * @Author: sjx
 * @Date: 2019年08月31日 0031 15:03:34
 */
public class CRC16Util {

    /**
     * 获取校验码byte数组，基于Modbus CRC16的校验算法
     *
     * @param arr_buff 字节数组
     * @return 校验码
     */
    public static byte[] getCrc16(byte[] arr_buff) {
        int len = arr_buff.length;

        // 预置 1 个 16 位的寄存器为十六进制FFFF, 称此寄存器为 CRC寄存器。
        int crc = 0xFFFF;
        int i, j;
        for (i = 0; i < len; i++) {
            // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
            crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
            for (j = 0; j < 8; j++) {
                // 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位
                if ((crc & 0x0001) > 0) {
                    // 如果移出位为 1, CRC寄存器与多项式A001进行异或
                    crc = crc >> 1;
                    crc = crc ^ 0xA001;
                } else {
                    // 如果移出位为 0,再次右移一位
                    crc = crc >> 1;
                }
            }
        }
        return intToBytes(crc);
    }

    /**
     * 获取16进制校验码，基于Modbus CRC16的校验算法
     * <p>
     * 报文数据：77 00 70 00 01 00 00 00 00 00 20 18 10 30 11 12 41 E5 73
     * 校验码：E5 73
     * </p>
     *
     * @param data
     * @return
     */
    public static String getCrc16ByStr(String data) {
        data = data.replace(" ", "");
        int len = data.length();
        if (!(len % 2 == 0)) {
            return "0000";
        }
        int num = len / 2;
        byte[] para = new byte[num];
        for (int i = 0; i < num; i++) {
            int value = Integer.valueOf(data.substring(i * 2, 2 * (i + 1)), 16);
            para[i] = (byte) value;
        }
        return HexUtil.encodeHexStr(getCrc16(para), false);
    }

    /**
     * 校验crc是否正确
     *
     * @param eq1
     * @param eq2
     * @return
     */
    public static boolean equalsCrc(short eq1, byte[] eq2) {
        return equalsCrc(getByteFromShort(eq1), changeHighLow(getCrc16(eq2)));
    }

    /**
     * 校验crc是否正确
     *
     * @param eq1
     * @param eq2
     * @return
     */
    public static boolean equalsCrc(byte[] eq1, byte[] eq2) {
        return Arrays.equals(eq1, eq2);
    }

    /**
     * 将int转换成byte数组，低位在前，高位在后 改变高低位顺序只需调换数组序号
     *
     * @param value
     * @return
     */
    private static byte[] intToBytes(int value) {
        byte[] src = new byte[2];
        src[1] = (byte) ((value >> 8) & 0xFF);
        src[0] = (byte) (value & 0xFF);
        return src;
    }

    /**
     * 改变高低位顺序
     *
     * @param b
     * @return
     */
    private static byte[] changeHighLow(byte[] b) {
        byte[] src = new byte[2];
        src[1] = b[0];
        src[0] = b[1];
        return src;
    }

    /**
     * 通过byte数组取到short
     *
     * @param b
     * @param index 第几位开始取
     * @return
     */
    private static short getShort(byte[] b, int index) {
        return (short) (((b[index + 1] << 8) | b[index + 0] & 0xff));
    }

    /**
     * 转换short为byte
     *
     * @param s 需要转换的short
     */
    private static byte[] getByteFromShort(short s) {
        byte[] b = new byte[2];
        b[1] = (byte) (s >> 8);
        b[0] = (byte) (s >> 0);
        return b;
    }


}